package utils;

import com.google.common.collect.Lists;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
 
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author zzx
 * 重试工具类
 */
@Slf4j
public class RetryUtil {
 
   private static final List<ScheduleConfig> scheduleConfigList = Lists.newArrayList();
 
   /**
    * 初始化重试计划
    */
   static {
      // 不间隔重试3次 
      //scheduleConfigList.add(new ScheduleConfig(0, TimeUnit.SECONDS, 3));
      // 间隔15秒后重试3次
      scheduleConfigList.add(new ScheduleConfig(15, TimeUnit.SECONDS, 3));
      // 每个1分钟重试一次
     // scheduleConfigList.add(new ScheduleConfig(1, TimeUnit.MINUTES, Integer.MAX_VALUE));
   }
 
   /**
    * 函数式接口FunctionHandler.apply调用
    * @param handler
    * @param <T>
    * @return
    */
   public static <T> T apply(FunctionHandler handler) {
      int i = 1;
      while (true) {
         try {
            return (T) handler.apply();
         } catch (Exception e) {
            if (++i > 3) {
               throw e;
            }
         }
      }
   }
 
   /**
    * 函数式接口FunctionHandler.apply实现
    */
   public static <T> T applyThrow(FunctionHandlerThrow handler) throws Exception {
      int i = 1;
      while (true) {
         try {
            return (T) handler.apply();
         } catch (Exception e) {
            if (++i > 3) {
               throw e;
            }
         }
      }
   }
 
   /**
    * 函数式接口ConsumerHandler.accept调用
    * @param handler
    */
   public static void accept(ConsumerHandler handler) {
      int i = 1;
      while (true) {
         try {
            handler.accept();
            return;
         } catch (Exception e) {
            if (++i > 3) {
               throw e;
            }
         }
      }
   }
 
   /**
    * 有输入有输出场景下的默认重试计划
    * @param handler
    * @param <T>
    * @return
    */
   public static <T> T scheduleApply(FunctionHandler<T> handler) {
      // 默认计划：先重试3次，再隔10s重试3次，再每分钟重试3次
      return scheduleApply(handler, scheduleConfigList);
   }
 
   /**
    * 有输入无输出场景下的重试计划
    * @param handler
    */
   public static void scheduleAccept(ConsumerHandler handler) {
      // 默认计划：先重试3次，再隔10s重试3次，再每分钟重试3次
      scheduleAccept(handler, scheduleConfigList);
   }
 
   /**
    * 自定义重试计划（有输入有输出场景使用）
    * @param handler
    * @param scheduleConfigList
    * @param <T>
    */
   public static <T> T scheduleApply(FunctionHandler handler, List<ScheduleConfig> scheduleConfigList) {
      int i = 1;
      int index = 0;
      ScheduleConfig scheduleConfig = scheduleConfigList.get(index);
      while (i<4) {
         try {
            return (T) handler.apply();
         } catch (Exception e) {
            if (i++ >= scheduleConfig.getRetryTimes()) {
               i++;
               //scheduleConfig = scheduleConfigList.get(++index);
            }
            scheduleConfig.sleep();
         }
      }
      throw new RuntimeException("请求失败");
   }
 
   /**
    * 自定义重试计划（有输入无输出的场景）
    * @param handler
    * @param scheduleConfigList
    */
   public static void scheduleAccept(ConsumerHandler handler, List<ScheduleConfig> scheduleConfigList) {
      int i = 1;
      int index = 0;
      ScheduleConfig scheduleConfig = scheduleConfigList.get(index);
      while (i<4) {
         try {
            handler.accept();
            return;
         } catch (Exception e) {
            if (i++ >= scheduleConfig.getRetryTimes()) {
               i++;
               //scheduleConfig = scheduleConfigList.get(++index);
            }
            scheduleConfig.sleep();
         }
      }
      throw new RuntimeException();
   }
 
   /**
    * 定义有输入无输出类函数式接口
    */
   @FunctionalInterface
   public interface ConsumerHandler {
      void accept();
   }
 
   /**
    * 定义有输入有输出类函数式接口
    * @param <T>
    */
   @FunctionalInterface
   public interface FunctionHandler<T> {
      T apply();
   }
 
   /**
    * 定义有断言类函数式接口
    * @param 
    */
   @FunctionalInterface
   public interface PredicateHandler {
      void test();
   }
 
   @FunctionalInterface
   public interface FunctionHandlerThrow<T> {
      T apply() throws Exception;
   }
   
   /**
    * 重试验证
    * @param <T>
    */
   public static void main(String[] args) {
      RetryUtil.scheduleApply(() -> accept());
   }
   
   private static String accept() {
       System.out.println(LocalDateTime.now());
       throw new RuntimeException("hhaa");
   }
}
 
/**
 * 重试计划Bean
 */
@Slf4j
@Data
class ScheduleConfig {
 
   public ScheduleConfig(Integer time, TimeUnit timeUnit, Integer retryTimes) {
      this.time = time;
      this.timeUnit = timeUnit;
      this.retryTimes = retryTimes;
   }
 
   /**
    * 重试间隔时间
    */
   private Integer time;
 
   /**
    * 重试间隔时间单位
    */
   private TimeUnit timeUnit;
 
   /**
    * 重试次数
    */
   private Integer retryTimes;
 
   public void sleep() {
      try {
         timeUnit.sleep(time);
      } catch (InterruptedException e) {
         log.error("retry sleep interrupted", e);
      }
   }
}